#include "stdafx.h"
#include "ServerSystem.h"
#include "ServerHandler.h"
#include "ClientHandler.h"

#include "Poco\Exception.h"
#include "Poco\Delegate.h"
#include "Poco\Any.h"
#include "Poco\File.h"
#include "Poco\DateTime.h"
#include "Poco\Timezone.h"
#include "Poco\DateTimeFormatter.h"
#include "Poco\MD5Engine.h"
#include "Poco\DigestStream.h"
#include "Poco\DigestEngine.h"
#include "Poco\Random.h"
#include "Poco\RandomStream.h"
#include "Poco\XML\XMLStream.h"

#include "Core\ServerConnection.h"
#include "Core\ClientConnection.h"
#include "Core\XMLConnection.h"
#include "Core\Packages.h"
#include "Core\CoreXMLHandlerManager.h"
#include "Core\Logger.h"
#include "Core\SafeMutex.h"

#include <sstream>
#include <stack>

namespace ExamSystem
{
    using Poco::Exception;
    using Poco::Delegate;
    using Poco::Any;
    using Poco::AnyCast;
    using Poco::MD5Engine;
    using Poco::DigestEngine;
    using Poco::DateTime;
    using Poco::Timezone;
    using Poco::DigestOutputStream;

namespace Server
{
    CServerSystem theServerSystem;
    static CServerSystem::StudentInfo nullStudentInfo = { "", "" };

    CServerSystem::CServerSystem(void)
        : m_listeningPort(12321)
        , m_flagRootServer(true)
        , m_flagConnectionRedirected(false)
        , m_flagValuntaryClosure(false)
        , m_flagInitialized(false)
        , m_serverInfo(new ServerInfo)
    {
        m_examInformation.examStatus = ExamInformation::Preparing;
        m_examInformation.examQuestionGenerationMethod = ExamInformation::Random;
        for (int i = 0; i < QuestionTypeCount; i++)
            m_examInformation.examQuestionCoefs[i] = 1.0;
        m_examInformation.examRestTime = 0;
        m_examInformation.examTotalTime = 7200;
        m_examInformation.examStartTime = 0;
        m_examInformation.examEndTime = 0;
    }

    CServerSystem::~CServerSystem(void)
    {
        if (m_flagInitialized)
            Finalize();
    }

    bool CServerSystem::Initialize(bool flagRootServer)
    {
        // check
        if (m_questionLibraryFile.empty() || m_answerDatabaseFile.empty())
        {
            systemLogger.fatal("The question library and/or the answer database haven't been specified.");
            return false;
        }
        srand((unsigned int)time(NULL));

        m_flagRootServer = flagRootServer;

        m_serverConnectionManager = new CExamServerConnectionManager;
        m_serverConnectionManager->SetPort(m_listeningPort);

        m_serverConnectionManager->GetHandlerManager()->RegisterStanzaHandler(CIdentityPackage::packageType, this);
        m_serverConnectionManager->eventConnectionClosed +=
            Delegate<CServerSystem, int>(this, &CServerSystem::OnClientConnectionClosed);

        // initialize the system logger
        if (m_flagRootServer)
        {
            CExamLogger::CreateLogger("RootServer.log");
            systemLogger.information("Root server start.");
        }
        else
        {
            CExamLogger::CreateLogger("Server.log");
            systemLogger.information("Server start.");
        }

        // initialize database
        if (InitializeDatabase())
        {
            systemLogger.information("Question library version: " + m_questionLibraryVersion);
        }
        else if (m_flagRootServer)
        {
            systemLogger.fatal("Can't open question library " + m_questionLibraryFile);
            return false;
        }
        
        if (m_flagRootServer)
        {
            Poco::File file(m_answerDatabaseFile);
            if (file.exists())
            {
                // this is a re-start. Get the runtime status from database
                if (!m_answerDatabase.Initialize(m_answerDatabaseFile))
                {
                    systemLogger.fatal("Can't open answer database " + m_answerDatabaseFile);
                    return false;
                }

                m_answerDatabase.LoadExamInformation(m_examInformation);
            }
            else
            {
                // we should create an answer database here
                Poco::DateTime dateTime;
                dateTime.makeLocal(Poco::Timezone::utcOffset());
                std::string str = Poco::DateTimeFormatter::format(dateTime, "Start @ %Y-%m-%d %W %H:%M:%S");
                MD5Engine md5;
                DigestOutputStream dos(md5);
                dos << str;
                dos.close();
                m_examInformation.examIdentity = DigestEngine::digestToHex(md5.digest());

                if (!m_answerDatabase.CreateDatabase(m_answerDatabaseFile, m_examInformation))
                {
                    systemLogger.fatal("Can't create answer database " + m_answerDatabaseFile);
                    return false;
                }
            }
        }
        

        // start
        if (m_flagRootServer)
        {
            // for root server, we needn't connect to another server, 
            // so we start the server part directly
            StartServer();
        }
        else
        {
            // for sub-server, we should connect to the root server first
            // the server handler will start the server part
            if (!ConnectServer())
            {
                systemLogger.fatal("Can't connect to server.");
                return false;
            }
        }

        m_flagInitialized = true;

        return true;
    }

    bool CServerSystem::InitializeDatabase(void)
    {
        bool ret = m_questionLibrary.Initialize(m_questionLibraryFile, false);
        if (ret)
        {
            m_questionLibraryVersion = m_questionLibrary.GetDatabaseVersion();
            for (int i = 0; i < QuestionTypeCount; i++)
                m_questionLibrary.LoadAllQuestion(static_cast<QuestionType>(i), m_questionIDs[i]);
        }

        return ret;
    }

    void CServerSystem::Finalize(void)
    {
        if (!m_flagInitialized)
            return;

        StopServer();

        if (!m_flagRootServer)
            DisconnectServer();

        m_questionLibrary.Finalize();
        m_answerDatabase.Finalize();

        systemLogger.information("Server stopped.");
        m_flagInitialized = false;
    }


    void CServerSystem::SetListeningPort(Poco::UInt16 port)
    {
        m_listeningPort = port;
    }

    void CServerSystem::SetRootServerAddress(const std::string &host, Poco::UInt16 port)
    {
        m_rootServerAddress = SocketAddress(host, port);
    }

    void CServerSystem::SetQuestionLibraryPath(const std::string &path)
    {
        m_questionLibraryFile = path;
    }


    void CServerSystem::StartServer(void)
    {
        if (m_serverConnectionManager)
        {
            m_serverConnectionManager->StartServer();
            eventServerStartListening(this, m_listeningPort);

            // send connection info
            if (!m_flagRootServer)
            {
                CPackagePtr pkg = packageManager.CreatePackage(CConnectionInfoPackage::packageType);
                CConnectionInfoPackage &info = *static_cast<CConnectionInfoPackage *>(pkg.get());

                info.m_sourceIdentity = CConnectionInfoPackage::Server;
                info.m_connectionStatus = CConnectionInfoPackage::Connected;
                info.m_serverHostString = m_serverConnectionManager->GetListenAddress();
                info.m_routePath.push_back(systemID);

                if (m_serverConnection && m_serverConnection->GetXMLConnection())
                    info.WriteToStream(m_serverConnection->GetXMLConnection()->GetSocketOutputStream());
            }
        }
    }

    void CServerSystem::StopServer(void)
    {
        if (m_serverConnectionManager)
            m_serverConnectionManager->StopServer();
    }

    bool CServerSystem::ConnectServer(void)
    {
        if (m_flagRootServer)
            return false;

        // for sub-server, we should create connection to root server;
        m_serverConnection = new CExamClientConnection;
        m_serverConnection->SetServerAddress(m_rootServerAddress);

        if (!m_serverConnection->ConnectServer())
        {
            eventServerConnectionClosed(this, this);
            return false;
        }

        m_serverConnection->eventConnectionClosed += 
            Delegate<CServerSystem, int>(this, &CServerSystem::OnServerConnectionClosed);
        m_serverConnection->eventConnectionDisconnected +=
            Delegate<CServerSystem, int>(this, &CServerSystem::OnServerConnectionDisconnected);
        m_serverConnection->eventConnectionReconnected +=
            Delegate<CServerSystem, int>(this, &CServerSystem::OnServerConnectionReconnected);

        CreateHandler();

        return true;
    }

    bool CServerSystem::CreateHandler(void)
    {
        m_serverConnectionHandler = new CServerHandler(this);
        m_serverConnectionHandler->eventConnectionRedirected +=
            Delegate<CServerSystem, SocketAddress>(this, &CServerSystem::OnServerConnectionRedirected);
        m_serverConnectionHandler->eventVersionCheckFailed +=
            Delegate<CServerSystem, void *const>(this, &CServerSystem::OnServerConnectionVersionCheckFailed);
        m_serverConnectionHandler->eventSystemIDUpdated +=
            Delegate<CServerSystem, void *const>(this, &CServerSystem::OnSystemIDUpdated);
        m_serverConnectionHandler->eventReady +=
            Delegate<CServerSystem, void *const>(this, &CServerSystem::OnServerConnectionReady);

        // send server identity
        SharedPtr<CPackage> pkg = packageManager.CreatePackage(CIdentityPackage::packageType);
        static_cast<CIdentityPackage *>(pkg.get())->m_identity = CIdentityPackage::Server;
        if (m_serverConnection && m_serverConnection->GetXMLConnection())
            pkg->WriteToStream(m_serverConnection->GetXMLConnection()->GetSocketOutputStream());

        m_serverConnectionHandler->StartHandler();
        eventServerConnectionConnected(this, this);
        return true;
    }

    void CServerSystem::DisconnectServer(void)
    {
        m_flagValuntaryClosure = true;
        if (m_serverConnection)
            m_serverConnection->DisconnectServer();
    }

    void CServerSystem::startElement(CXMLConnection *connection, const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
    {
        if (localName == CPackage::labelPackage)
            m_currentPackage = packageManager.CreatePackage(attributes.getValue(CPackage::namespaceURI, CPackage::attributeType));

        if (m_currentPackage)
            m_currentPackage->startElement(uri, localName, qname, attributes);
    }

    void CServerSystem::endElement(CXMLConnection *connection, const XMLString& uri, const XMLString& localName, const XMLString& qname)
    {
        if (m_currentPackage)
            m_currentPackage->endElement(uri, localName, qname);

        if (localName == CPackage::labelPackage && m_currentPackage)
        {
            CSafeScopedLock lock(m_connectionLock);
            CIdentityPackage *pkg = dynamic_cast<CIdentityPackage *>(m_currentPackage.get());

            if (pkg)
            {
                switch (pkg->m_identity)
                {
                case CIdentityPackage::Server:
                    m_clientServerConnection.push_back(connection->GetLocalID());

                    m_serverInfo->connectedServers.push_back(new ServerInfo);
                    m_serverInfo->connectedServers.back()->serverID = m_currentPackage->m_sourseID;
                    m_serverInfo->connectedServers.back()->serverAddress = connection->GetSocket().peerAddress();
                    m_serverInfo->connectedServers.back()->serverRedirections = 0;

                    m_clientServerHandlers.push_back(new CClientServerHandler(
                        this, 
                        m_serverConnectionManager->GetXMLConnection(connection->GetLocalID()),
                        connection->GetLocalID()
                        ));

                    m_clientServerHandlers.back()->eventFailedToSendDatabase += 
                        Delegate<CServerSystem, void *const>(this, &CServerSystem::OnClientConnectionError);
                    m_clientServerHandlers.back()->eventConnectionInfoUpdated +=
                        Delegate<CServerSystem, CConnectionInfoPackage>(this, &CServerSystem::OnClientConnectionInfoUpdated);
                    m_clientServerHandlers.back()->eventReady +=
                        Delegate<CServerSystem, void *const>(this, &CServerSystem::OnClientServerReady);
                    m_clientServerHandlers.back()->StartHandler();

                    eventClientServerConnected(this, m_serverInfo->connectedServers.size());
                    break;

                case CIdentityPackage::Client:
                    m_clientConnection.push_back(connection->GetLocalID());

                    m_serverInfo->connectedClients.push_back(new ClientInfo);
                    m_serverInfo->connectedClients.back()->clientID = m_currentPackage->m_sourseID;
                    m_serverInfo->connectedClients.back()->clientAddress = connection->GetSocket().peerAddress();

                    m_clientHandlers.push_back(new CClientHandler(
                        this,
                        m_serverConnectionManager->GetXMLConnection(connection->GetLocalID()), 
                        connection->GetLocalID()
                        ));

                    m_clientHandlers.back()->eventConnectionInfoUpdated +=
                        Delegate<CServerSystem, CConnectionInfoPackage>(this, &CServerSystem::OnClientConnectionInfoUpdated);

                    m_clientHandlers.back()->StartHandler();

                    eventClientConnected(this, m_serverInfo->connectedClients.size());
                    break;
                }
            }

            // reset the package
            m_currentPackage = NULL;
        }
    }

    void CServerSystem::characters(CXMLConnection *connection, const XMLChar ch[], int start, int length)
    {
        if (m_currentPackage)
            m_currentPackage->characters(ch, start, length);
    }

    void CServerSystem::OnSystemIDUpdated(const void *, void *const&)
    {
        m_serverInfo->serverID = systemID;
    }

    void CServerSystem::OnClientConnectionClosed(const void* pSender, int &id)
    {
        CSafeScopedLock lock(m_connectionLock);

        std::vector<int>::iterator it = std::find(m_clientConnection.begin(), m_clientConnection.end(), id);
        if (it != m_clientConnection.end())
        {
            size_t idx = it - m_clientConnection.begin();
            eventClientDisconnected(this, idx);

            SendDisconnectInfo(false, idx);
            m_clientHandlers.erase(m_clientHandlers.begin() + idx);
            m_serverInfo->connectedClients.erase(m_serverInfo->connectedClients.begin() + idx);
            m_clientConnection.erase(it);
        }
        else
        {
            it = std::find(m_clientServerConnection.begin(), m_clientServerConnection.end(), id);
            if (it != m_clientServerConnection.end())
            {
                size_t idx = it - m_clientServerConnection.begin();
                eventClientServerDisconnected(this, idx);

                SendDisconnectInfo(true, idx);
                m_clientServerHandlers.erase(m_clientServerHandlers.begin() + idx);
                m_serverInfo->connectedServers.erase(m_serverInfo->connectedServers.begin() + idx);
                m_clientServerConnection.erase(it);
            }
        }

        if (!m_flagRootServer && m_examInformation.examStatus == ExamInformation::Finished &&
            m_clientServerConnection.empty() && m_clientConnection.empty())
        {
            SendAnswerDatabase();
        }
    }

    void CServerSystem::OnClientConnectionError(const void* pSender, void *const& pConnection)
    {
        CSystemHandler *handler = reinterpret_cast<CSystemHandler *>(pConnection);
        handler->m_xmlConnection.get()->EndStream();
    }

    void CServerSystem::OnClientServerReady(const void *pServer, void *const&)
    {
    }

    void CServerSystem::OnClientConnectionInfoUpdated(const void *pSender, CConnectionInfoPackage &info)
    {
        CSafeScopedLock lock(m_connectionLock);
        if (info.m_connectionStatus == CConnectionInfoPackage::Connected)
        {
            if (info.m_routePath.size() == 1)
            {
                if (info.m_sourceIdentity == CConnectionInfoPackage::Server)
                {
                    int id = reinterpret_cast<const CClientServerHandler *>(pSender)->GetLocalID();
                    std::vector<int>::iterator it = std::find(m_clientServerConnection.begin(), m_clientServerConnection.end(), id);
                    id = (int)(it - m_clientServerConnection.begin());

                    Poco::UInt16 port;
                    std::stringstream sstr(info.m_serverHostString.substr(info.m_serverHostString.find_last_of(':') + 1));
                    sstr >> port;
                    Poco::Net::IPAddress host = m_serverInfo->connectedServers[id]->serverAddress.host();
                    m_serverInfo->connectedServers[id]->serverAddress = SocketAddress(host, port);
                    m_serverInfo->connectedServers[id]->serverID = info.m_sourseID;
                    info.m_serverHostString = m_serverInfo->connectedServers[id]->serverAddress.toString();
                    eventClientServerInfoUpdated(this, id);

                    // if there is any client that connected before the sub-servers' connecting,
                    // we should redirect it to the sub-server
                    if (m_serverInfo->connectedServers.size() == 1)
                    {
                        CPackagePtr pkg = packageManager.CreatePackage(CRedirectPackage::packageType);
                        CRedirectPackage &redirect = *static_cast<CRedirectPackage *>(pkg.get());
                        redirect.m_redirected = true;
                        redirect.m_targetAddress = m_serverInfo->connectedServers.front()->serverAddress;
                        for (size_t i = 0; i < m_serverInfo->connectedClients.size(); i++)
                        {
                            m_clientHandlers[i]->SendPackage(&redirect);
                        }
                    }

                }
                else
                {
                    int id = reinterpret_cast<const CClientHandler *>(pSender)->GetLocalID();
                    std::vector<int>::iterator it = std::find(m_clientConnection.begin(), m_clientConnection.end(), id);
                    id = (int)(it - m_clientConnection.begin());

                    m_serverInfo->connectedClients[id]->loginStudent.studentName = info.m_studentName;
                    m_serverInfo->connectedClients[id]->loginStudent.studentNumber = info.m_studentNumber;
                    eventStudentLogin(this, id);
                }
            }
            else
            {                
                if (info.m_sourceIdentity == CConnectionInfoPackage::Server)
                {
                    std::vector<int> path;
                    SharedPtr<ServerInfo> serverInfo = GetClientServerInfo(info.m_routePath, path, true, true);
                    if (serverInfo)
                    {
                        serverInfo->connectedServers.push_back(new ServerInfo);
                        serverInfo->connectedServers.back()->serverID = info.m_routePath.front();
                        serverInfo->connectedServers.back()->serverAddress = SocketAddress(info.m_serverHostString);

                        path.push_back(serverInfo->connectedServers.size() - 1);
                        eventClientServerConnectedInfo(this, path);
                    }
                }
                else
                {
                    std::vector<int> path;
                    SharedPtr<ServerInfo> serverInfo = GetClientServerInfo(info.m_routePath, path, false, true);
                    if (serverInfo)
                    {
                        serverInfo->connectedClients.push_back(new ClientInfo);
                        serverInfo->connectedClients.back()->clientID = info.m_routePath.front();
                        serverInfo->connectedClients.back()->loginStudent.studentName = info.m_studentName;
                        serverInfo->connectedClients.back()->loginStudent.studentNumber = info.m_studentNumber;

                        path.push_back(serverInfo->connectedClients.size() - 1);
                        eventClientConnectedInfo(this, path);
                    }
                }
            }
        }
        else
        {
            // disconnected
            std::vector<int> path;
            SharedPtr<ServerInfo> serverInfo = GetClientServerInfo(
                info.m_routePath, path, 
                info.m_sourceIdentity == CConnectionInfoPackage::Server, 
                false
                );

            if (serverInfo)
            {
                if (info.m_sourceIdentity == CConnectionInfoPackage::Server)
                {
                    eventClientServerDisconnectedInfo(this, path);
                    serverInfo->connectedServers.erase(serverInfo->connectedServers.begin() + path.back());
                }
                else
                {
                    eventClientDisconnectedInfo(this, path);
                    serverInfo->connectedClients.erase(serverInfo->connectedClients.begin() + path.back());
                }
            }
        }

        // resend
        if (!m_flagRootServer)
        {
            info.m_routePath.push_back(systemID);
            if (m_serverConnection && m_serverConnection->GetXMLConnection())
                info.WriteToStream(m_serverConnection->GetXMLConnection()->GetSocketOutputStream());
        }
    }

    SharedPtr<CServerSystem::ServerInfo> CServerSystem::GetClientServerInfo(const std::vector<int> &rpath, std::vector<int> &path, bool server, bool newConnection)
    {
        SharedPtr<ServerInfo> serverInfo = m_serverInfo;
        path.clear();
        for (size_t i = rpath.size() - 1; i >= 1 ; i--)
        {
            bool found = false;
            int serverID = rpath[i];
            for (size_t j = 0; j < serverInfo->connectedServers.size(); j++)
            {
                if (serverInfo->connectedServers[j]->serverID == serverID)
                {
                    path.push_back(j);
                    serverInfo = serverInfo->connectedServers[j];
                    found = true;
                    break;
                }
            }
            if (!found)
                break;
        }
        if (path.size() < rpath.size() - 1)
        {
            path.clear();
            return NULL;
        }

        if (newConnection)
            return serverInfo;

        int id = rpath.front();
        if (server)
        {
            for (size_t j = 0; j < serverInfo->connectedServers.size(); j++)
            {
                if (serverInfo->connectedServers[j]->serverID == id)
                {
                    path.push_back(j);
                    break;
                }
            }
        }
        else
        {          
            for (size_t j = 0; j < serverInfo->connectedClients.size(); j++)
            {
                if (serverInfo->connectedClients[j]->clientID == id)
                {
                    path.push_back(j);
                    break;
                }
            }
        }

        if (path.size() < rpath.size())
        {
            path.clear();
            return NULL;
        }

        return serverInfo;
    }

    void CServerSystem::OnServerConnectionClosed(const void* pSender, int &id)
    {
        eventServerConnectionDisconnected(this, this);
        m_serverConnectionHandler = NULL;

        if (m_flagValuntaryClosure)
        {
            eventServerConnectionClosed(this, this);
            return;
        }

        m_zombieServerConnection = m_serverConnection;

        if (m_flagConnectionRedirected)
            systemLogger.error("Disconnected from server, trying to reconnect root server...");
        else
            systemLogger.error("Disconnected from root server, trying to reconnect...");

        static const int maxRetryTime = 10;
        static const int sleepTime = 5000;
        int nRetryTime = 0;
        while (true)
        {
            if (ConnectServer())
                break;
            
            nRetryTime++;
            if (nRetryTime < maxRetryTime)
            {
                systemLogger.error(Poco::format("The (%d/%d)-th try failed. Wait 5 seconds to retry...", nRetryTime, maxRetryTime));
                Thread::current()->sleep(sleepTime);
            }
            else
            {   
                systemLogger.error(Poco::format("The (%d/%d)-th try failed.", nRetryTime, maxRetryTime));
                break;
            }            
        }

        if (nRetryTime == maxRetryTime)
        {
            // failed
            systemLogger.fatal("Failed to reconnect.");
            eventServerConnectionClosed(this, this);
        }
    }

    void CServerSystem::OnServerConnectionReconnected(const void* pSender, int &id)
    {
        CreateHandler();
        // we don't send it here, we send in OnServerReady()
        //SendAllConnectionInfo();
        m_flagConnectionRedirected = true;
    }

    void CServerSystem::OnServerConnectionDisconnected(const void *pSender, int &id)
    {
        eventServerConnectionDisconnected(this, this);
        m_serverConnectionHandler = NULL;
    }

    void CServerSystem::OnServerConnectionRedirected(const void *pSender, SocketAddress &targetAddress)
    {
        if (targetAddress.host().isLoopback())
            targetAddress = SocketAddress(m_rootServerAddress.host(), targetAddress.port());

        m_serverConnection->SetServerAddress(targetAddress);
        m_serverConnection->ReconnectServer();
    }

    void CServerSystem::OnServerConnectionVersionCheckFailed(const void *pSender, void *const& pConnection)
    {
        m_serverConnection->DisconnectServer(false);
    }

    const std::string & CServerSystem::GetQuestionLibraryVersion(void) const
    {
        return m_questionLibraryVersion;
    }

    const std::string & CServerSystem::GetQuestionLibraryPath(void) const
    {
        return m_questionLibraryFile;
    }

    void CServerSystem::SendExamOrder(COrderPackage &order)
    {
        // the exam order should be sent to all connections
        CSafeScopedLock lock(m_connectionLock);
        for (size_t i = 0; i < m_clientHandlers.size(); i++)
            m_clientHandlers[i]->SendPackage(&order);
        for (size_t i = 0; i < m_clientServerHandlers.size(); i++)
            m_clientServerHandlers[i]->SendPackage(&order);
    }

    void CServerSystem::SendExamOrder(COrderPackage::ExamOrder order)
    {
        CPackagePtr ptr = packageManager.CreatePackage(COrderPackage::packageType);
        COrderPackage &od = *static_cast<COrderPackage *>(ptr.get());
        od.m_order = order;
        SendExamOrder(od);
    }

    void CServerSystem::SendExamMessage(CMessagePackage &message)
    {
        // the exam message should be sent to all connections
        CSafeScopedLock lock(m_connectionLock);
        for (size_t i = 0; i < m_clientHandlers.size(); i++)
            m_clientHandlers[i]->SendPackage(&message);
        for (size_t i = 0; i < m_clientServerHandlers.size(); i++)
            m_clientServerHandlers[i]->SendPackage(&message);
    }

    void CServerSystem::SendExamMessage(const std::string &message, CMessagePackage::MessageType type)
    {
        CPackagePtr ptr = packageManager.CreatePackage(CMessagePackage::packageType);
        CMessagePackage &msg = *static_cast<CMessagePackage *>(ptr.get());
        msg.m_messageBody = message;
        msg.m_messageType = type;
        SendExamMessage(msg);
    }

    void CServerSystem::SendExamInformation(CInformationPackage &information)
    {
        // send exam information to all connections
        CSafeScopedLock lock(m_connectionLock);
        for (size_t i = 0; i < m_clientHandlers.size(); i++)
            m_clientHandlers[i]->SendPackage(&information);
        for (size_t i = 0; i < m_clientServerHandlers.size(); i++)
            m_clientServerHandlers[i]->SendPackage(&information);
    }

    void CServerSystem::RespondToOrder(COrderPackage &order)
    {
        switch (order.m_order)
        {
        case COrderPackage::StartExam:
            m_examInformation.examStatus = ExamInformation::Processing;
            m_examInformation.examStartTime = (unsigned int)time(NULL);
            m_examInformation.examEndTime = m_examInformation.examStartTime + m_examInformation.examTotalTime;
            eventExamStart(this, 0);
            break;

        case COrderPackage::StopExam:
            m_examInformation.examStatus = ExamInformation::Finished;

            // this function will only be called by sub-server, which didn't create/open
            // an answer database at the starting time.
            // we create answer database here for receiving students' answer
            m_answerDatabase.CreateDatabase(m_answerDatabaseFile, m_examInformation);
            eventExamStop(this, 0);
            break;
        }
    }

    void CServerSystem::SetExamInformation(const ExamInformation &information)
    {
        m_examInformation.examIdentity = information.examIdentity;
        m_examInformation.examStartTime = information.examStartTime;
        m_examInformation.examStatus = information.examStatus;
        m_examInformation.examQuestionGenerationMethod = information.examQuestionGenerationMethod;
        for (int i = 0; i < QuestionTypeCount; i++)
        {
            m_examInformation.examQuestions[i] = information.examQuestions[i];
            m_examInformation.examQuestionCoefs[i] = information.examQuestionCoefs[i];
        }
        m_examInformation.examTotalTime = information.examTotalTime;
        m_examInformation.examRestTime = information.examRestTime;

        m_examInformation.examStartTime = 0;
        m_examInformation.examEndTime = 0;

        if (m_examInformation.examStatus == ExamInformation::Processing)
        {
            unsigned int currentTime = (unsigned int)time(NULL);
            m_examInformation.examStartTime = currentTime - (m_examInformation.examTotalTime - m_examInformation.examRestTime);
            m_examInformation.examEndTime = currentTime + m_examInformation.examRestTime;
        }

        // note this function will only be called by sub-server, 
        // if the exam has already stopped, we should create/re-open the answer database
        if (m_examInformation.examStatus == ExamInformation::Finished)
        {
            if (!m_answerDatabase.Initialize(m_answerDatabaseFile))
                m_answerDatabase.CreateDatabase(m_answerDatabaseFile, m_examInformation);
        }
    }

    ExamInformation & CServerSystem::GetExamInformation(void)
    {
        switch (m_examInformation.examStatus)
        {
        case ExamInformation::Preparing:
            m_examInformation.examRestTime = m_examInformation.examTotalTime;
            break;
        case ExamInformation::Processing:
            m_examInformation.examRestTime = m_examInformation.examEndTime - (unsigned int)time(NULL);
            break;
        case ExamInformation::Finished:
            m_examInformation.examRestTime = 0;
            break;
        }
        return m_examInformation;
    }

    CLoginHelper::OperationResult CServerSystem::StudentLogin(CClientHandler *client, const std::string &name, const std::string &number)
    {
        CLoginHelper::OperationResult result = m_loginHelper.StudentLogin(name, number);
        return result;
    }

    CLoginHelper::OperationResult CServerSystem::InvigilatorLogin(CClientHandler *client, const std::string &user, const std::string &password)
    {
        CLoginHelper::OperationResult result = m_loginHelper.InvigilatorLogin(user, password);
        return result;
    }

    bool CServerSystem::IsExamStart(void) const
    {
        return m_examInformation.examStatus == ExamInformation::Processing;
    }

    bool CServerSystem::IsExamFinished(void) const
    {
        return m_examInformation.examStatus == ExamInformation::Finished;
    }

    bool CServerSystem::RedirectConnection(CSystemHandler *handler, SocketAddress &address)
    {
        CSafeScopedLock lock(m_connectionLock);
        if (m_serverInfo->connectedServers.empty())
            return false;

        if (dynamic_cast<CClientServerHandler *>(handler))
        {
            // this is a sub-server, we needn't to redirect it
            return false;
        }

        // now this is a client, we should redirect it

        std::vector<SharedPtr<CClientHandler> >::iterator it = 
            std::find(m_clientHandlers.begin(), m_clientHandlers.end(), handler);

        size_t idx = it - m_clientHandlers.begin();
        Poco::Net::IPAddress ip = m_serverInfo->connectedClients[idx]->clientAddress.host();

        // strategy:
        // 1. the client and the sub-server are in the same subnet (with mask 255.255.255.0)
        // 2. average the client count redirected to each sub-server
        Poco::Net::IPAddress ipMask("255.255.255.0");
        ip.mask(ipMask);
        //std::string ipstr = ip.toString();
        std::vector<size_t> candidates;
        for (size_t i = 0; i < m_serverInfo->connectedServers.size(); i++)
        {
            //std::string cipstr = m_serverInfo->connectedServers[i]->serverAddress.host().toString();
            Poco::Net::IPAddress addr = m_serverInfo->connectedServers[i]->serverAddress.host();
            addr.mask(ipMask);
            if (addr == ip)
                candidates.push_back(i);
        }

        size_t target = 0;
        if (candidates.empty())
        {
            int minRd = INT_MAX;
            for (size_t i = 0; i < m_serverInfo->connectedServers.size(); i++)
            {
                if (minRd > m_serverInfo->connectedServers[i]->serverRedirections)
                {
                    target = i;
                    minRd = m_serverInfo->connectedServers[i]->serverRedirections;
                }
            }        
        }
        else
        {
            int minRd = INT_MAX;
            for (size_t i = 0; i < candidates.size(); i++)
            {
                if (minRd > m_serverInfo->connectedServers[candidates[i]]->serverRedirections)
                {
                    target = candidates[i];
                    minRd = m_serverInfo->connectedServers[candidates[i]]->serverRedirections;
                }
            }        
        }

        address = m_serverInfo->connectedServers[target]->serverAddress;

        return true;
    }


    Poco::UInt16 CServerSystem::GetListeningPort(void) const
    {
        return m_listeningPort;
    }

    bool CServerSystem::IsInitialized(void) const
    {
        return m_flagInitialized;
    }

    std::string CServerSystem::GetClientServerAddress(int idx)
    {
        CSafeScopedLock lock(m_connectionLock);
        if (idx < 0 || idx >= (int) m_serverInfo->connectedServers.size())
            return "";
        return m_serverInfo->connectedServers[idx]->serverAddress.toString();
    }

    std::string CServerSystem::GetClientServerAddress(const std::vector<int> &path)
    {
        SharedPtr<ServerInfo> ptr = m_serverInfo;
        for (size_t i = 0; i < path.size(); i++)
            ptr = ptr->connectedServers[path[i]];
        return ptr->serverAddress.toString();
    }

    std::string CServerSystem::GetClientAddress(int idx)
    {
        CSafeScopedLock lock(m_connectionLock);
        if (idx < 0 || idx >= (int) m_serverInfo->connectedClients.size())
            return "";
        return m_serverInfo->connectedClients[idx]->clientAddress.toString();
    }

    std::string CServerSystem::GetClientAddress(const std::vector<int> &path)
    {
        SharedPtr<ServerInfo> ptr = m_serverInfo;
        for (size_t i = 0; i < path.size() - 1; i++)
            ptr = ptr->connectedServers[path[i]];
        return ptr->connectedClients[path.back()]->clientAddress.toString();
    }

    const CServerSystem::StudentInfo &CServerSystem::GetStudentInfo(int idx)
    {
        CSafeScopedLock lock(m_connectionLock);
        if (idx < 0 || idx >= (int) m_serverInfo->connectedClients.size())
        {
            return nullStudentInfo;
        }
        return m_serverInfo->connectedClients[idx]->loginStudent;
    }

    const CServerSystem::StudentInfo & CServerSystem::GetStudentInfo(const std::vector<int> &path)
    {
        SharedPtr<ServerInfo> ptr = m_serverInfo;
        for (size_t i = 0; i < path.size() - 1; i++)
            ptr = ptr->connectedServers[path[i]];
        return ptr->connectedClients[path.back()]->loginStudent;
    }

    void CServerSystem::SendDisconnectInfo(bool server, int idx)
    {
        if (m_flagRootServer)
            return;

        // send connection info
        CPackagePtr pkg = packageManager.CreatePackage(CConnectionInfoPackage::packageType);
        CConnectionInfoPackage &info = *static_cast<CConnectionInfoPackage *>(pkg.get());

        info.m_sourceIdentity = server ? CConnectionInfoPackage::Server : CConnectionInfoPackage::Client;
        info.m_connectionStatus = CConnectionInfoPackage::Disconnected;
        info.m_routePath.push_back(
            server ? m_serverInfo->connectedServers[idx]->serverID : m_serverInfo->connectedClients[idx]->clientID
            );
        info.m_routePath.push_back(systemID);

        if (m_serverConnection && m_serverConnection->GetXMLConnection())
            info.WriteToStream(m_serverConnection->GetXMLConnection()->GetSocketOutputStream());
    }

    void CServerSystem::SendAllConnectionInfo(void)
    {
        if (m_flagRootServer)
            return;

        CSafeScopedLock lock(m_connectionLock);
        
        // send connection info
        CPackagePtr pkg = packageManager.CreatePackage(CConnectionInfoPackage::packageType);
        CConnectionInfoPackage &info = *static_cast<CConnectionInfoPackage *>(pkg.get());

        info.m_connectionStatus = CConnectionInfoPackage::Connected;

        std::vector<int> path;
        SendConnectionInfo(path, info, m_serverInfo);
    }

    void CServerSystem::SendConnectionInfo(std::vector<int> &path, CConnectionInfoPackage &info, SharedPtr<ServerInfo> &serverInfo)
    {
        if (!serverInfo)
            return;

        SharedPtr<SafeSocketOutputStream> &stream = m_serverConnection->GetXMLConnection()->GetSocketOutputStream();
        path.push_back(serverInfo->serverID);
        path.push_back(-1);

        for (size_t i = 0; i < serverInfo->connectedClients.size(); i++)
        {
            info.m_sourceIdentity = CConnectionInfoPackage::Client;

            ClientInfo &clientInfo = *serverInfo->connectedClients[i];
            info.m_studentName = clientInfo.loginStudent.studentName;
            info.m_studentNumber = clientInfo.loginStudent.studentNumber;
            path.back() = clientInfo.clientID;
            info.m_routePath = path;
            std::reverse(info.m_routePath.begin(), info.m_routePath.end());
            info.WriteToStream(stream);
        }

        for (size_t i = 0; i < serverInfo->connectedServers.size(); i++)
        {
            info.m_sourceIdentity = CConnectionInfoPackage::Server;

            ServerInfo &subServerInfo = *serverInfo->connectedServers[i];
            info.m_serverHostString = subServerInfo.serverAddress.toString();
            path.back() = subServerInfo.serverID;
            info.m_routePath = path;
            std::reverse(info.m_routePath.begin(), info.m_routePath.end());
            info.WriteToStream(stream);
        }

        path.pop_back();
        for (size_t i = 0; i < serverInfo->connectedServers.size(); i++)
            SendConnectionInfo(path, info, serverInfo->connectedServers[i]);

        path.pop_back();
    }

    void CServerSystem::OnServerConnectionReady(const void *, void *const&)
    {
        StartServer();
        SendAllConnectionInfo();
    }

    void CServerSystem::SetAnswerDatabasePath(const std::string &path)
    {
        m_answerDatabaseFile = path;
    }

    CQuestionLibrary & CServerSystem::GetQuestionLibrary(void)
    {
        return m_questionLibrary;
    }

    CAnswerDatabase & CServerSystem::GetAnswerDatabase(void)
    {
        return m_answerDatabase;
    }

    void CServerSystem::ApplyExamInformation(void)
    {
        // just send
        CPackagePtr pkg = packageManager.CreatePackage(CInformationPackage::packageType);
        CInformationPackage &info = *static_cast<CInformationPackage *>(pkg.get());
        info.m_information = m_examInformation;
        SendExamInformation(info);

        if (m_answerDatabase.IsInitialized())
            m_answerDatabase.SaveExamInformation(m_examInformation);
    }

    void CServerSystem::StartExam(void)
    {
        if (m_examInformation.examStatus != ExamInformation::Preparing)
            return;

        // save this time
        Poco::DateTime dateTime;
        dateTime.makeLocal(Poco::Timezone::utcOffset());
        std::string str = Poco::DateTimeFormatter::format(dateTime, "%Y-%m-%d %W %H:%M:%S");
        m_examInformation.examDateTime = str;
        m_answerDatabase.SaveExamInformation(m_examInformation);
        
        // time
        m_examInformation.examStartTime = (unsigned int)time(NULL);
        m_examInformation.examEndTime = m_examInformation.examStartTime + m_examInformation.examTotalTime;
        m_examInformation.examStatus = ExamInformation::Processing;
        m_examInformation.examRestTime = m_examInformation.examTotalTime;

        // we should send the exam information first
        CPackagePtr pkg = packageManager.CreatePackage(CInformationPackage::packageType);
        CInformationPackage &info = *static_cast<CInformationPackage *>(pkg.get());
        info.m_information = m_examInformation;
        SendExamInformation(info);
        SendExamOrder(COrderPackage::StartExam);
        systemLogger.information("The examination started.");   
    }

    void CServerSystem::StopExam(void)
    {
        if (m_examInformation.examStatus != ExamInformation::Processing)
            return;

        SendExamOrder(COrderPackage::StopExam);
        m_examInformation.examStatus = ExamInformation::Finished;
        systemLogger.information("The examination stopped.");
    }

    void CServerSystem::GenerateQuestions(std::vector<int> questionIds[QuestionTypeCount])
    {
        if (m_examInformation.examQuestionGenerationMethod == ExamInformation::Specified)
        {
            for (int i = 0; i < QuestionTypeCount; i++)
                questionIds[i] = m_examInformation.examQuestions[i];
        }
        else
        {
            int idx;
            std::set<int> questionSet;
            for (int i = 0; i < QuestionTypeCount; i++)
            {
                if (m_examInformation.examQuestions[i].empty())
                    continue;

                if (m_questionIDs[i].size() <= m_examInformation.examQuestions[i].front())
                {
                    questionIds[i] = m_questionIDs[i];
                    continue;
                }
                questionIds[i].clear();
                questionSet.clear();
                while (questionSet.size() < m_examInformation.examQuestions[i].front())
                {
                    idx = rand();
                    idx %= m_questionIDs[i].size();
                    size_t s = questionSet.size();
                    questionSet.insert(idx);
                    while (questionSet.size() == s)
                    {
                        questionSet.insert(idx);
                        idx++;
                        idx %= m_questionIDs[i].size();
                    }
                }
                
                for (std::set<int>::iterator it = questionSet.begin();
                    it != questionSet.end(); it++)
                {
                    questionIds[i].push_back(m_questionIDs[i][*it]);
                }

            }
        }
    }

    bool CServerSystem::MergeAnswerDatabase(const std::string &name)
    {
        CSafeScopedLock scope(m_databaseLock);
        return m_answerDatabase.MergeDatabase(name);
    }

    void CServerSystem::SendAnswerDatabase(void)
    {
        CSafeScopedLock scope(m_databaseLock);
        CPackagePtr pkg = packageManager.CreatePackage(CDatabasePackage::packageType);
        CDatabasePackage &database = StaticCast<CDatabasePackage>(pkg);
        database.SetDatabaseFile(m_answerDatabaseFile);
        m_serverConnectionHandler->SendPackage(pkg);
    }

    void CServerSystem::SetLoginHelperProperty(const std::string &property, const std::string &value)
    {
        m_loginHelper.SetProperty(property, value);
    }




}
}